Skip to content

Conversation

@minggangw
Copy link
Member

@minggangw minggangw commented Aug 25, 2025

This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2 turtle_tf2_py package in a modern web-based interface.

Key changes:

  • Adds a complete electron demonstration with TF2 coordinate frame visualization
  • Provides interactive turtle control with keyboard input (WASD keys)
  • Implements multiple TF2 broadcasters (static, dynamic, fixed frames) with real-time updates
  • Updates main README to highlight the new electron visualization capabilities

Fix: #1230, #1231

Copilot AI review requested due to automatic review settings August 25, 2025 06:37

This comment was marked as outdated.

@minggangw minggangw requested a review from Copilot August 25, 2025 06:48
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2 turtle_tf2_py package in a modern web-based interface.

Key changes:

  • Adds a complete electron demonstration with TF2 coordinate frame visualization
  • Provides interactive turtle control with keyboard input (WASD keys)
  • Implements multiple TF2 broadcasters (static, dynamic, fixed frames) with real-time updates
  • Updates main README to highlight the new electron visualization capabilities

Reviewed Changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
electron_demo/turtle_tf2/renderer.js Main Three.js renderer handling 3D visualization, turtle management, and keyboard controls
electron_demo/turtle_tf2/package.json Package configuration with dependencies for electron, rclnodejs, and three.js
electron_demo/turtle_tf2/orbit-controls.js Three.js OrbitControls implementation for 3D camera navigation
electron_demo/turtle_tf2/main.js Electron main process with ROS2 nodes for TF2 broadcasting and turtle control
electron_demo/turtle_tf2/index.html Web interface with control panels, status displays, and 3D viewport
electron_demo/turtle_tf2/README.md Comprehensive documentation for the turtle TF2 demo
README.md Updated main documentation to feature electron visualization capabilities

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +312 to +328
function updateFrame(name, transform) {
const position = transform.transform.translation;
const rotation = transform.transform.rotation;

if (!frames[name]) {
// Determine color based on frame name
let color = 0xffffff;
let scale = 0.5; // Default scale

if (name.includes('static')) {
color = 0xff4444;
scale = 1.0; // Bigger for static frame
} else if (name.includes('dynamic')) {
color = 0xff6600; // Bright orange color
scale = 3.0; // MUCH bigger for dynamic frame - very easy to see!
console.log('🟠 Creating DYNAMIC FRAME at:', position);
} else if (name.includes('fixed')) {
Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple calls to name.includes() can be inefficient when checking frame names repeatedly. Consider using a more efficient approach like a Map or Set for frame type lookup, or caching the frame type determination.

Suggested change
function updateFrame(name, transform) {
const position = transform.transform.translation;
const rotation = transform.transform.rotation;
if (!frames[name]) {
// Determine color based on frame name
let color = 0xffffff;
let scale = 0.5; // Default scale
if (name.includes('static')) {
color = 0xff4444;
scale = 1.0; // Bigger for static frame
} else if (name.includes('dynamic')) {
color = 0xff6600; // Bright orange color
scale = 3.0; // MUCH bigger for dynamic frame - very easy to see!
console.log('🟠 Creating DYNAMIC FRAME at:', position);
} else if (name.includes('fixed')) {
// Helper function to determine frame type
function getFrameType(name) {
if (name.includes('dynamic')) {
return 'dynamic';
} else if (name.includes('static')) {
return 'static';
} else if (name.includes('fixed')) {
return 'fixed';
}
return 'default';
}
function updateFrame(name, transform) {
const position = transform.transform.translation;
const rotation = transform.transform.rotation;
const frameType = getFrameType(name);
if (!frames[name]) {
// Determine color and scale based on frame type
let color = 0xffffff;
let scale = 0.5; // Default scale
if (frameType === 'static') {
color = 0xff4444;
scale = 1.0; // Bigger for static frame
} else if (frameType === 'dynamic') {
color = 0xff6600; // Bright orange color
scale = 3.0; // MUCH bigger for dynamic frame - very easy to see!
console.log('🟠 Creating DYNAMIC FRAME at:', position);
} else if (frameType === 'fixed') {

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +357
const timer = node.createTimer(1000, () => {
// Wrap the async logic in a try-catch to handle promise rejections
(async () => {
try {
// Don't automatically spawn turtle2 - let user control this via UI
// This prevents service call errors if turtlesim is not running

// Simple following logic (in real implementation, this would use TF lookup)
// For demo purposes, we'll simulate the transform lookup behavior
if (turtleTf2Nodes.turtle2Spawned) {
// This is a simplified version - in real TF2, we'd lookup transforms
// For the demo, we'll let the renderer handle the following logic
if (mainWindow) {
mainWindow.webContents.send('request-turtle-follow');
}
}
} catch (error) {
console.error('Timer callback error:', error);
}
})();
Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an immediately invoked async function expression inside a timer callback can make error handling and debugging more difficult. Consider defining the async function separately and calling it, or restructuring to avoid the nested async pattern.

Suggested change
const timer = node.createTimer(1000, () => {
// Wrap the async logic in a try-catch to handle promise rejections
(async () => {
try {
// Don't automatically spawn turtle2 - let user control this via UI
// This prevents service call errors if turtlesim is not running
// Simple following logic (in real implementation, this would use TF lookup)
// For demo purposes, we'll simulate the transform lookup behavior
if (turtleTf2Nodes.turtle2Spawned) {
// This is a simplified version - in real TF2, we'd lookup transforms
// For the demo, we'll let the renderer handle the following logic
if (mainWindow) {
mainWindow.webContents.send('request-turtle-follow');
}
}
} catch (error) {
console.error('Timer callback error:', error);
}
})();
async function handleTurtleTf2Timer() {
try {
// Don't automatically spawn turtle2 - let user control this via UI
// This prevents service call errors if turtlesim is not running
// Simple following logic (in real implementation, this would use TF lookup)
// For demo purposes, we'll simulate the transform lookup behavior
if (turtleTf2Nodes.turtle2Spawned) {
// This is a simplified version - in real TF2, we'd lookup transforms
// For the demo, we'll let the renderer handle the following logic
if (mainWindow) {
mainWindow.webContents.send('request-turtle-follow');
}
}
} catch (error) {
console.error('Timer callback error:', error);
}
}
const timer = node.createTimer(1000, () => {
handleTurtleTf2Timer();

Copilot uses AI. Check for mistakes.
Comment on lines +421 to +441
if (key in keyState) {
keyState[key] = true;
event.preventDefault();
}
});

document.addEventListener('keyup', (event) => {
// Only handle WASD keys (case-insensitive)
const key = event.key.toLowerCase();
if (key in keyState) {
keyState[key] = false;
event.preventDefault();
}
});

// Send movement commands based on key state
setInterval(() => {
if (demoState.rosConnected) {
sendTurtleCommand();
}
}, 100); // Send commands at 10Hz
Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using setInterval at 100ms (10Hz) for continuous command sending can be inefficient. Consider implementing a more responsive approach that only sends commands when keys are actually pressed/released, or use requestAnimationFrame for smoother updates.

Suggested change
if (key in keyState) {
keyState[key] = true;
event.preventDefault();
}
});
document.addEventListener('keyup', (event) => {
// Only handle WASD keys (case-insensitive)
const key = event.key.toLowerCase();
if (key in keyState) {
keyState[key] = false;
event.preventDefault();
}
});
// Send movement commands based on key state
setInterval(() => {
if (demoState.rosConnected) {
sendTurtleCommand();
}
}, 100); // Send commands at 10Hz
if (key in keyState && !keyState[key]) {
keyState[key] = true;
event.preventDefault();
if (demoState.rosConnected) {
sendTurtleCommand();
}
}
});
document.addEventListener('keyup', (event) => {
// Only handle WASD keys (case-insensitive)
const key = event.key.toLowerCase();
if (key in keyState && keyState[key]) {
keyState[key] = false;
event.preventDefault();
if (demoState.rosConnected) {
// If no movement keys are pressed, send stop command
if (!keyState.w && !keyState.a && !keyState.s && !keyState.d) {
sendTurtleStopCommand();
} else {
sendTurtleCommand();
}
}
}
});

Copilot uses AI. Check for mistakes.
@coveralls
Copy link

Coverage Status

coverage: 84.555%. remained the same
when pulling 624f05d on minggangw:add-tf2-electron-demo
into b6c7e42 on RobotWebTools:develop.

@minggangw minggangw merged commit e014a6c into RobotWebTools:develop Aug 25, 2025
19 checks passed
minggangw added a commit that referenced this pull request Sep 10, 2025
This PR adds a comprehensive TF2 electron demo that showcases coordinate frame transformations, turtle simulation, and real-time 3D visualization using rclnodejs, Electron, and Three.js. The demo replicates the functionality of the ROS2 `turtle_tf2_py` package in a modern web-based interface.

Key changes:
- Adds a complete electron demonstration with TF2 coordinate frame visualization
- Provides interactive turtle control with keyboard input (WASD keys)
- Implements multiple TF2 broadcasters (static, dynamic, fixed frames) with real-time updates
- Updates main README to highlight the new electron visualization capabilities

Fix: #1230, #1231
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a TF2 demo based on Electron

2 participants